package mirrormonkey.util.annotations.parsing.memberlevel;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;

import mirrormonkey.util.annotations.control.IRClass;
import mirrormonkey.util.annotations.parsing.AnnotationParser;
import mirrormonkey.util.annotations.parsing.ClassIR;
import mirrormonkey.util.annotations.parsing.FieldIR;
import mirrormonkey.util.annotations.parsing.MemberIR;

import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Contains test cases concerning the collection of annotations on membersById in
 * classes that exist within a hierarchy.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class HierarchicalMemberIRCollectionTest {

	public static AnnotationParser parser;

	@BeforeClass
	public static void setUpBeforeClass() {
		parser = new AnnotationParser();
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation {
	}

	@Retention(RetentionPolicy.RUNTIME)
	public static @interface CustomAnnotation2 {
	}

	public static class CollectingProgIR extends MemberIR {

		public CollectingProgIR(AnnotationParser parser, Field member) {
			super(parser, member);
			addCollectType(CustomAnnotation.class);
		}

		public CollectingProgIR(FieldIR previous, Field member) {
			super(previous, member);
			addCollectType(CustomAnnotation.class);
		}

		@Override
		public String toString() {
			return "[" + super.toString() + ": collected: "
					+ this.collectedAnnotations + "]";
		}

		@Override
		public boolean matches(Member member) {
			return false;
		}

		@Override
		public void parseField(Field f) {
			super.collectAnnotations(f);
		}

		@Override
		public void parseMethod(Method m) {
			super.collectAnnotations(m);
		}

		@Override
		public void parseConstructor(Constructor<?> constr) {
		}

		@Override
		public Class<?>[] getListenMemberClasses() {
			return new Class<?>[] { Constructor.class, Field.class,
					Method.class };
		}

	}

	public static class DeclareCollectingIR {

		@CustomAnnotation
		@IRClass(CollectingProgIR.class)
		public Object field;
	}

	/**
	 * Tests if annotations are collected from a single member if the user
	 * declares it in a custom <tt>AnnotationIR</tt>.
	 */
	@Test
	public void testAcceptProgrammatically() {
		ClassIR ir = parser.parseClass(DeclareCollectingIR.class, null);
		PresetMemberTest.checkContainsMembers(ir,
				new Class<?>[][] { { CustomAnnotation.class } }, 3);
	}

	public static class DeclareCollection {

		@CustomAnnotation
		@IRClass(value = FieldIR.class, startCollect = CustomAnnotation.class)
		public Object field;

	}

	/**
	 * Tests if annotations are collected from a single member if the user
	 * declares it by <tt>IRClass</tt>.
	 */
	@Test
	public void testAcceptDeclaratively() {
		ClassIR ir = parser.parseClass(DeclareCollection.class, null);
		PresetMemberTest.checkContainsMembers(ir,
				new Class<?>[][] { { CustomAnnotation.class } });
	}

	public static class DeclareOtherThanCollected {

		@CustomAnnotation2
		@IRClass(CollectingProgIR.class)
		public Object field;

	}

	/**
	 * Tests if false positive annotations are not collected if some are
	 * declared in a custom <tt>AnnotationIR</tt>.
	 */
	@Test
	public void testRejectProgrammatically() {
		ClassIR ir = parser.parseClass(DeclareOtherThanCollected.class, null);
		PresetMemberTest.checkContainsMembers(ir, new Class<?>[][] { {} }, 3);
	}

	public static class DeclareOtherThanCollected2 {

		@CustomAnnotation2
		@IRClass(value = FieldIR.class, startCollect = { CustomAnnotation.class })
		public Object field;

	}

	/**
	 * Tests if false positive annotations are not collected if some are
	 * declared for collection in a <tt>IRClass</tt>.
	 */
	@Test
	public void testRejectDeclaratively() {
		ClassIR ir = parser.parseClass(DeclareOtherThanCollected2.class, null);
		PresetMemberTest.checkContainsMembers(ir, new Class<?>[][] { {} });
	}

	public static class DeclareBothWays {

		@CustomAnnotation
		@CustomAnnotation2
		@IRClass(value = CollectingProgIR.class, startCollect = { CustomAnnotation2.class })
		public Object field;

	}

	/**
	 * Tests if annotation classes are collected if the user combines both
	 * custom <tt>AnnotationIR</tt> and <tt>IRClass</tt> declaration methods.
	 */
	@Test
	public void testAcceptCombinedDeclaration() {
		ClassIR ir = parser.parseClass(DeclareBothWays.class, null);
		PresetMemberTest.checkContainsMembers(ir, new Class<?>[][] { {
				CustomAnnotation.class, CustomAnnotation2.class } }, 3);
	}

	public static class DeclareTwoFields {

		@CustomAnnotation
		@IRClass(value = FieldIR.class, startCollect = {
				CustomAnnotation.class, CustomAnnotation2.class })
		public Object field;

		@CustomAnnotation2
		@IRClass(value = FieldIR.class, startCollect = {
				CustomAnnotation.class, CustomAnnotation2.class })
		public Object field2;

	}

	/**
	 * Tests if annotations collected for one member will not interfere with
	 * annotations collected for another.
	 */
	@Test
	public void testMultiMemberCollection() {
		ClassIR ir = parser.parseClass(DeclareTwoFields.class, null);
		PresetMemberTest.checkContainsMembers(ir, new Class<?>[][] {
				{ CustomAnnotation.class }, { CustomAnnotation2.class } });
	}

	public static class ExtendingCollectingIR extends DeclareCollection {

		@CustomAnnotation2
		@IRClass(startCollect = CustomAnnotation2.class)
		public Object field;

	}

	/**
	 * Tests if collected annotations cumulate over multiple classes in the
	 * hierarchy.
	 */
	@Test
	public void testMultiLevelCollection() {
		ClassIR ir = parser.parseClass(ExtendingCollectingIR.class, null);
		PresetMemberTest.checkContainsMembers(ir, new Class<?>[][] { {
				CustomAnnotation.class, CustomAnnotation2.class } });
	}

	public static class CollectBoth {

		@CustomAnnotation
		@IRClass(value = FieldIR.class, startCollect = {
				CustomAnnotation.class, CustomAnnotation2.class })
		public Object field;

		@IRClass(value = FieldIR.class, startCollect = {
				CustomAnnotation.class, CustomAnnotation2.class })
		public Object field2;

	}

	public static class ExtendCollectBoth extends CollectBoth {

		@CustomAnnotation2
		@IRClass(stopCollect = { CustomAnnotation.class,
				CustomAnnotation2.class })
		public Object field;

		@CustomAnnotation
		@CustomAnnotation2
		@IRClass(stopCollect = { CustomAnnotation2.class })
		public Object field2;

	}

	/**
	 * Tests if we can stop collecting annotations of a certain kind, but if
	 * they will not get removed from the already collected annotations either.
	 */
	@Test
	public void testStopCollect() {
		ClassIR ir = parser.parseClass(ExtendCollectBoth.class, null);
		PresetMemberTest.checkContainsMembers(ir, new Class<?>[][] {
				{ CustomAnnotation.class }, { CustomAnnotation.class } });
	}

	public static class ExtendCollectBothAndRemove extends ExtendCollectBoth {

		@IRClass(removeCompletely = CustomAnnotation.class)
		public Object field;

		@CustomAnnotation
		@IRClass(removeCompletely = CustomAnnotation.class)
		public Object field2;

	}

	/**
	 * Tests if collected annotations are removed if requested by an
	 * <tt>IRClass</tt> annotation
	 */
	@Test
	public void testRemoveCollected() {
		ClassIR ir = parser.parseClass(ExtendCollectBothAndRemove.class, null);
		PresetMemberTest.checkContainsMembers(ir, new Class<?>[][] { {},
				{ CustomAnnotation.class } });
	}
}
